Apgūstiet Python dizaina modeļus: Singleton, Factory, Observer. Rokasgrāmata aptver ieviešanu, lietošanu un labāko praksi ar kodu piemēriem.
Izstrādātāja rokasgrāmata par Python dizaina modeļiem: Singleton, Factory un Observer
Programmatūras inženierijas pasaulē kodēšana, kas vienkārši darbojas, ir tikai pirmais solis. Programmatūras radīšana, kas ir mērogojama, viegli uzturama un elastīga, ir profesionāla izstrādātāja raksturīgākā iezīme. Šeit talkā nāk dizaina modeļi. Tie nav specifiski algoritmi vai bibliotēkas, bet gan augsta līmeņa, no valodas neatkarīgi risinājumi biežākajām programmatūras dizaina problēmām.
Šī visaptverošā rokasgrāmata sniegs jums padziļinātu ieskatu trīs no fundamentālākajiem un plaši izmantotajiem dizaina modeļiem, kas ieviesti Python valodā: Singleton, Factory un Observer. Mēs izpētīsim, kas tie ir, kāpēc tie ir noderīgi un kā tos efektīvi ieviest savos Python projektos.
Kas ir dizaina modeļi un kāpēc tie ir svarīgi?
Pirmoreiz konceptualizēti "Četru banda" (GoF) savā nozīmīgajā grāmatā, "Design Patterns: Elements of Reusable Object-Oriented Software", dizaina modeļi ir pārbaudīti risinājumi atkārtotām dizaina problēmām. Tie nodrošina kopīgu vārdu krājumu izstrādātājiem, ļaujot komandām efektīvāk apspriest sarežģītus arhitektūras risinājumus.
Dizaina modeļu izmantošana noved pie:
- Palielināta atkārtota izmantojamība: Labi izstrādātas komponentes var atkārtoti izmantot dažādos projektos.
- Uzlabota uzturējamība: Kods kļūst organizētāks, vieglāk saprotams un mazāk pakļauts kļūdām, ja nepieciešamas izmaiņas.
- Uzlabota mērogojamība: Arhitektūra ir elastīgāka, ļaujot sistēmai augt, neprasot pilnīgu pārrakstīšanu.
- Vaļīga saistība: Komponentes ir mazāk atkarīgas viena no otras, veicinot modularitāti un neatkarīgu izstrādi.
Sāksim mūsu izpēti ar izveides modeli, kas kontrolē objektu instancēšanu: Singleton.
Singleton modelis: Viena instance, lai pārvaldītu visus
Kas ir Singleton modelis?
Singleton modelis ir izveides modelis, kas nodrošina, ka klasei ir tikai viena instance un tā nodrošina vienotu, globālu piekļuves punktu. Iedomājieties sistēmas mēroga konfigurācijas pārvaldnieku, žurnālēšanas pakalpojumu vai datu bāzes savienojumu kopumu. Jūs nevēlētos, lai šiem komponentiem būtu vairākas neatkarīgas instances; jums ir nepieciešams viens autoritatīvs avots.
Singleton galvenie principi ir:
- Viena instance: Klase var tikt instancēta tikai vienu reizi visā lietojumprogrammas darbības laikā.
- Globālā piekļuve: Pastāv mehānisms, lai piekļūtu šai unikālajai instancei no jebkuras koda daļas.
Kad to izmantot (un kad no tā izvairīties)
Singleton modelis ir jaudīgs, taču bieži tiek pārmērīgi izmantots. Ir svarīgi saprast tā piemērotos lietošanas gadījumus un būtiskos trūkumus.
Labi lietošanas gadījumi:
- Žurnālēšana: Viens žurnālēšanas objekts var centralizēt žurnālu pārvaldību, nodrošinot, ka visas lietojumprogrammas daļas koordinēti raksta vienā failā vai pakalpojumā.
- Konfigurācijas pārvaldība: Lietojumprogrammas konfigurācijas iestatījumiem (piemēram, API atslēgām, funkciju karogiem) jābūt ielādētiem vienreiz un globāli pieejamiem no viena patiesības avota.
- Datu bāzes savienojumu kopumi: Datu bāzes savienojumu kopuma pārvaldība ir resursietilpīgs uzdevums. Singleton var nodrošināt, ka kopums tiek izveidots vienu reizi un efektīvi koplietots visā lietojumprogrammā.
- Aparatūras saskarnes piekļuve: Sazinoties ar vienu aparatūras daļu, piemēram, printeri vai specifisku sensoru, singleton var novērst konfliktus no vairākiem vienlaicīgiem piekļuves mēģinājumiem.
Singletonu bīstamība (Pretmodeļa skatījums):
Neskatoties uz tā lietderību, Singleton bieži tiek uzskatīts par pretmodeli, jo tas:
- Pārkāpj Viena atbildības principu: Singleton klase ir atbildīga gan par savu pamatloģiku, gan par savas darbības cikla pārvaldību (nodrošinot vienu instanci).
- Ievieš globālo stāvokli: Globālais stāvoklis apgrūtina koda analīzi un atkļūdošanu. Izmaiņas vienā sistēmas daļā var radīt negaidītas blakusparādības citā.
- Traucē testējamību: Komponentes, kas paļaujas uz globālo singletonu, ir cieši saistītas ar to. Tas apgrūtina unit testēšanu, jo singletonu nevar viegli aizstāt ar mock objektu vai stub objektu izolētai testēšanai.
Eksperta padoms: Pirms ķerties pie Singleton, apsveriet, vai atkarību injekcija varētu atrisināt jūsu problēmu elegantāk. Vienas objekta instances (piemēram, konfigurācijas objekta) nodošana klasēm, kurām tā nepieciešama, var sasniegt to pašu mērķi bez globālā stāvokļa trūkumiem.
Singleton ieviešana Python valodā
Python piedāvā vairākus veidus, kā ieviest Singleton modeli, katram no tiem ir savas priekšrocības un trūkumi. Fascinējošs Python aspekts ir tas, ka tā moduļu sistēma pēc būtības darbojas kā singleton. Kad importējat moduli, Python ielādē un inicializē to tikai vienu reizi. Turpmākas tā paša moduļa importēšanas dažādās koda daļās atgriezīs atsauci uz to pašu moduļa objektu.
Apskatīsim explicit klases bāzētas implementācijas.
1. ieviešana: Izmantojot metaklasi
Metaklases izmantošana bieži tiek uzskatīta par visrobustāko un "pythonisko" veidu, kā ieviest singleton. Metaklase definē klases uzvedību, tāpat kā klase definē objekta uzvedību. Šeit mēs varam pārtvert klases izveides procesu.
class SingletonMeta(type):
"""A metaclass for creating a Singleton class."""
_instances = {}
def __call__(cls, *args, **kwargs):
# This method is called when an instance is created, e.g., MyClass()
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class GlobalConfig(metaclass=SingletonMeta):
def __init__(self):
# This will only be executed the first time the instance is created.
print("Initializing GlobalConfig...")
self.settings = {"api_key": "default_key", "timeout": 30}
def get_setting(self, key):
return self.settings.get(key)
# --- Usage ---
config1 = GlobalConfig()
config2 = GlobalConfig()
print(f"config1 settings: {config1.settings}")
config1.settings["api_key"] = "new_secret_key_12345"
print(f"config2 settings: {config2.settings}") # Will show the updated key
# Verify they are the same object
print(f"Are config1 and config2 the same instance? {config1 is config2}")
Šajā piemērā `SingletonMeta` `__call__` metode pārtver `GlobalConfig` instancēšanu. Tā uztur vārdnīcu `_instances` un nodrošina, ka tiek izveidota un saglabāta tikai viena `GlobalConfig` instance.
2. ieviešana: Izmantojot dekoratoru
Dekoratori nodrošina kodolīgāku un vieglāk lasāmu veidu, kā pievienot singleton uzvedību klasei, nemainot tās iekšējo struktūru.
def singleton(cls):
"""A decorator to turn a class into a Singleton."""
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self):
print("Connecting to the database...")
# Simulate a database connection setup
self.connection_id = id(self)
# --- Usage ---
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"DB1 Connection ID: {db1.connection_id}")
print(f"DB2 Connection ID: {db2.connection_id}")
print(f"Are db1 and db2 the same instance? {db1 is db2}")
Šī pieeja ir tīra un atdala singleton loģiku no pašas klases biznesa loģikas. Tomēr tai var būt dažas nianses saistībā ar mantošanu un introspekciju.
Factory modelis: Objektu izveides atsaistīšana
Tālāk mēs pārejam pie cita jaudīga izveides modeļa: Factory. Jebkura Factory modeļa pamatideja ir abstrahēt objektu izveides procesu. Tā vietā, lai objektus radītu tieši, izmantojot konstruktoru (piemēram, `my_obj = MyClass()`), jūs izsaucat rūpnīcas metodi. Tas atsaista jūsu klienta kodu no konkrētām klasēm, kuras tam ir jāinstancē.
Šī atsaistīšana ir neticami vērtīga. Iedomājieties, ka jūsu lietojumprogramma atbalsta datu eksportēšanu dažādos formātos, piemēram, PDF, CSV un JSON. Bez rūpnīcas jūsu klienta kods varētu izskatīties šādi:
if export_format == 'pdf':
exporter = PDFExporter()
elif export_format == 'csv':
exporter = CSVExporter()
else:
exporter = JSONExporter()
exporter.export(data)
Šis kods ir trausls. Ja pievienojat jaunu formātu (piemēram, XML), jums ir jāatrod un jāmaina katra vieta, kur šī loģika pastāv. Rūpnīca centralizē šo izveides loģiku.
Factory Method modelis
Factory Method modelis definē saskarni objekta izveidei, bet ļauj apakšklasēm mainīt izveidoto objektu tipu. Tas ir par instancēšanas atlikšanu apakšklasēm.
Struktūra:
- Produkts: Saskarne objektiem, ko rada rūpnīcas metode (piemēram, `Document`).
- Konkrēts produkts: Produkta saskarnes konkrētās implementācijas (piemēram, `PDFDocument`, `WordDocument`).
- Radītājs: Abstrakta klase, kas deklarē rūpnīcas metodi (`create_document()`). Tā var arī definēt veidnes metodi, kas izmanto rūpnīcas metodi.
- Konkrēts radītājs: Apakšklases, kas ignorē rūpnīcas metodi, lai atgrieztu specifiska Konkrēta produkta instanci (piemēram, `PDFCreator` atgriež `PDFDocument`).
Praktisks piemērs: Daudzplatformu UI rīku komplekts
Iedomāsimies, ka veidojam UI ietvaru, kam jāizveido dažādas pogas dažādām operētājsistēmām.
from abc import ABC, abstractmethod
# --- Product Interface and Concrete Products ---
class Button(ABC):
"""Product Interface: Defines the interface for buttons."""
@abstractmethod
def render(self):
pass
class WindowsButton(Button):
"""Concrete Product: A button with Windows OS style."""
def render(self):
print("Rendering a button in Windows style.")
class MacOSButton(Button):
"""Concrete Product: A button with macOS style."""
def render(self):
print("Rendering a button in macOS style.")
# --- Creator (Abstract) and Concrete Creators ---
class Dialog(ABC):
"""Creator: Declares the factory method.
It also contains business logic that uses the product.
"""
@abstractmethod
def create_button(self) -> Button:
"""The factory method."""
pass
def show_dialog(self):
"""The core business logic that isn't aware of concrete button types."""
print("Showing a generic dialog box.")
button = self.create_button()
button.render()
class WindowsDialog(Dialog):
"""Concrete Creator for Windows."""
def create_button(self) -> Button:
return WindowsButton()
class MacOSDialog(Dialog):
"""Concrete Creator for macOS."""
def create_button(self) -> Button:
return MacOSButton()
# --- Client Code ---
def initialize_app(os_name: str):
if os_name == "Windows":
dialog = WindowsDialog()
elif os_name == "macOS":
dialog = MacOSDialog()
else:
raise ValueError(f"Unsupported OS: {os_name}")
dialog.show_dialog()
# Simulate running the app on different OS
print("--- Running on Windows ---")
initialize_app("Windows")
print("\n--- Running on macOS ---")
initialize_app("macOS")
Ievērojiet, kā metode `show_dialog` darbojas ar jebkuru `Button`, nezinot tās konkrēto tipu. Lēmums par to, kuru pogu izveidot, tiek deleģēts `WindowsDialog` un `MacOSDialog` apakšklasēm. Tas padara `LinuxDialog` pievienošanu triviālu, nemainot `Dialog` klasi vai klienta kodu, kas to izmanto.
Abstraktās rūpnīcas modelis
Abstraktās rūpnīcas modelis iet vēl vienu soli tālāk. Tas nodrošina saskarni saistītu vai atkarīgu objektu saimes izveidei, nenorādot to konkrētās klases. Tas ir kā rūpnīca citu rūpnīcu radīšanai.
Turpinot mūsu UI piemēru, dialoglodziņam nav tikai poga; tam ir izvēles rūtiņas, teksta lauki un daudz kas cits. Konsekvents izskats un darbība (tēma) prasa, lai visi šie elementi piederētu tai pašai saimei (piemēram, visi Windows stila vai visi macOS stila).
Struktūra:
- Abstrakta rūpnīca: Saskarne ar rūpnīcas metožu kopumu abstraktu produktu radīšanai (piemēram, `create_button()`, `create_checkbox()`).
- Konkrēta rūpnīca: Implementē Abstrakto rūpnīcu, lai radītu konkrētu produktu saimi (piemēram, `LightThemeFactory`, `DarkThemeFactory`).
- Abstrakts produkts: Saskarnes katram atšķirīgam produktam saimē (piemēram, `Button`, `Checkbox`).
- Konkrēts produkts: Konkrētās implementācijas katrai produktu saimei (piemēram, `LightButton`, `DarkButton`, `LightCheckbox`, `DarkCheckbox`).
Praktisks piemērs: UI tēmas rūpnīca
from abc import ABC, abstractmethod
# --- Abstract Product Interfaces ---
class Button(ABC):
@abstractmethod
def paint(self):
pass
class Checkbox(ABC):
@abstractmethod
def paint(self):
pass
# --- Concrete Products for the 'Light' Theme ---
class LightButton(Button):
def paint(self):
print("Painting a light theme button.")
class LightCheckbox(Checkbox):
def paint(self):
print("Painting a light theme checkbox.")
# --- Concrete Products for the 'Dark' Theme ---
class DarkButton(Button):
def paint(self):
print("Painting a dark theme button.")
class DarkCheckbox(Checkbox):
def paint(self):
print("Painting a dark theme checkbox.")
# --- Abstract Factory Interface ---
class UIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_checkbox(self) -> Checkbox:
pass
# --- Concrete Factories for each theme ---
class LightThemeFactory(UIFactory):
def create_button(self) -> Button:
return LightButton()
def create_checkbox(self) -> Checkbox:
return LightCheckbox()
class DarkThemeFactory(UIFactory):
def create_button(self) -> Button:
return DarkButton()
def create_checkbox(self) -> Checkbox:
return DarkCheckbox()
# --- Client Code ---
class Application:
def __init__(self, factory: UIFactory):
self.factory = factory
self.button = None
self.checkbox = None
def create_ui(self):
self.button = self.factory.create_button()
self.checkbox = self.factory.create_checkbox()
def paint_ui(self):
self.button.paint()
self.checkbox.paint()
# --- Main application logic ---
def get_factory_for_theme(theme_name: str) -> UIFactory:
if theme_name == "light":
return LightThemeFactory()
elif theme_name == "dark":
return DarkThemeFactory()
else:
raise ValueError(f"Unknown theme: {theme_name}")
# Create and run the application with a specific theme
current_theme = "dark"
ui_factory = get_factory_for_theme(current_theme)
app = Application(ui_factory)
app.create_ui()
app.paint_ui()
Klase `Application` pilnībā nezina par tēmām. Tā vienkārši zina, ka tai ir nepieciešama `UIFactory`, lai iegūtu savus UI elementus. Jūs varat ieviest pilnīgi jaunu tēmu (piemēram, `HighContrastThemeFactory`), izveidojot jaunu produktu klašu kopumu un jaunu rūpnīcu, nekādā veidā nemainot `Application` klienta kodu.
Observer modelis: objektu informētība
Visbeidzot, apskatīsim stūrakmeni uzvedības modeli: Observer. Šis modelis definē vienam pret daudziem atkarību starp objektiem, tā ka, kad viens objekts (subjekts) maina stāvokli, visi tā atkarīgie (novērotāji) tiek automātiski paziņoti un atjaunināti.
Šis modelis ir notikumu vadītas programmēšanas pamats. Iedomājieties abonēšanu jaunumu vēstulēm, sekošanu kādam sociālajos medijos vai akciju cenu brīdinājumu saņemšanu. Katrā gadījumā jūs (novērotājs) reģistrējat savu interesi par tēmu, un jūs automātiski saņemat paziņojumu, kad notiek kaut kas jauns.
Galvenās sastāvdaļas: Subjekts un Novērotājs
- Subjekts (vai Observejams): Tas ir interesējošais objekts. Tas uztur savu novērotāju sarakstu un nodrošina metodes to pievienošanai (`subscribe`), atvienošanai (`unsubscribe`) un paziņošanai.
- Novērotājs (vai Abonents): Tas ir objekts, kas vēlas tikt informēts par izmaiņām. Tas definē atjaunināšanas saskarni, ko subjekts izsauc, kad mainās tā stāvoklis.
Kad to izmantot
- Notikumu apstrādes sistēmas: GUI rīku komplekti ir klasisks piemērs. Poga (subjekts) paziņo vairākiem klausītājiem (novērotājiem), kad tā tiek noklikšķināta.
- Paziņojumu pakalpojumi: Kad ziņu vietnē (subjekts) tiek publicēts jauns raksts, visi reģistrētie abonenti (novērotāji) saņem e-pastu vai push paziņojumu.
- Modelis-Skats-Kontrolieris (MVC) arhitektūra: Modelis (subjekts) paziņo Skatam (novērotājam) par jebkādām datu izmaiņām, lai Skats varētu atkārtoti renderēt sevi, lai parādītu atjaunināto informāciju. Tas saglabā datu loģiku un prezentācijas loģiku atsevišķi.
- Uzraudzības sistēmas: Sistēmas veselības monitors (subjekts) var paziņot dažādiem informācijas paneļiem un brīdinājuma sistēmām (novērotājiem), kad kritisks rādītājs (piemēram, CPU izmantošana vai atmiņa) pārsniedz slieksni.
Observer modeļa ieviešana Python valodā
Šeit ir praktiska ziņu aģentūras implementācija, kas paziņo dažāda veida abonentiem.
from abc import ABC, abstractmethod
from typing import List
# --- Observer Interface and Concrete Observers ---
class Observer(ABC):
@abstractmethod
def update(self, subject):
pass
class EmailNotifier(Observer):
def __init__(self, email_address: str):
self.email_address = email_address
def update(self, subject):
print(f"Sending Email to {self.email_address}: New story available! Title: '{subject.latest_story}'")
class SMSNotifier(Observer):
def __init__(self, phone_number: str):
self.phone_number = phone_number
def update(self, subject):
print(f"Sending SMS to {self.phone_number}: News Alert: '{subject.latest_story}'")
# --- Subject (Observable) Class ---
class NewsAgency:
def __init__(self):
self._observers: List[Observer] = []
self._latest_story: str = ""
def attach(self, observer: Observer) -> None:
print("News Agency: Attached an observer.")
self._observers.append(observer)
def detach(self, observer: Observer) -> None:
print("News Agency: Detached an observer.")
self._observers.remove(observer)
def notify(self) -> None:
print("News Agency: Notifying observers...")
for observer in self._observers:
observer.update(self)
@property
def latest_story(self) -> str:
return self._latest_story
def add_new_story(self, story: str) -> None:
print(f"\nNews Agency: Publishing new story: '{story}'")
self._latest_story = story
self.notify()
# --- Client Code ---
# Create the subject
agency = NewsAgency()
# Create observers
email_subscriber1 = EmailNotifier("reader1@example.com")
sms_subscriber1 = SMSNotifier("+15551234567")
email_subscriber2 = EmailNotifier("another.reader@example.com")
# Attach observers to the subject
agency.attach(email_subscriber1)
agency.attach(sms_subscriber1)
agency.attach(email_subscriber2)
# The subject's state changes, and all observers are notified
agency.add_new_story("Global Tech Summit Begins Next Week")
# Detach an observer
agency.detach(email_subscriber1)
# Another state change occurs
agency.add_new_story("Breakthrough in Renewable Energy Announced")
Šajā piemērā `NewsAgency` nav jāzina nekas par `EmailNotifier` vai `SMSNotifier`. Tas zina tikai to, ka tie ir `Observer` objekti ar `update` metodi. Tas rada ļoti atsaistītu sistēmu, kurā varat pievienot jaunus paziņojumu veidus (piemēram, `PushNotifier`, `SlackNotifier`), nemainot `NewsAgency` klasi.
Secinājums: Labākas programmatūras veidošana ar dizaina modeļiem
Mēs esam izpētījuši trīs pamata dizaina modeļus – Singleton, Factory un Observer – un redzējuši, kā tos var ieviest Python valodā, lai atrisinātu biežākās arhitektūras problēmas.
- Singleton modelis nodrošina vienu, globāli pieejamu instanci, kas ir ideāli piemērota koplietojamu resursu pārvaldībai, taču tas jāizmanto piesardzīgi, lai izvairītos no globālā stāvokļa trūkumiem.
- Factory modeļi (Factory Method un Abstract Factory) nodrošina jaudīgu veidu, kā atsaistīt objektu izveidi no klienta koda, padarot mūsu sistēmas modulārākas un paplašināmākas.
- Observer modelis nodrošina tīru, notikumu vadītu arhitektūru, ļaujot objektiem abonēt un reaģēt uz stāvokļa izmaiņām citos objektos, veicinot vaļīgu saistību.
Dizaina modeļu apguves atslēga nav iegaumēt to implementācijas, bet gan saprast problēmas, kuras tie atrisina. Saskaroties ar dizaina problēmu, padomājiet, vai zināms modelis var nodrošināt robustu, elegantu un viegli uzturamu risinājumu. Integrējot šos modeļus savā izstrādātāja rīku komplektā, jūs varat rakstīt kodu, kas ir ne tikai funkcionāls, bet arī tīrs, noturīgs un gatavs nākotnes izaugsmei.